# 1.什么是类？
# Python中自定义对象类型，就是 定义一个类 ， 类 就是 类型 的意思。
# 类名 首字母大写，这里定义的类名就是 BenzCar
# 下面定义的 brand, country 都是 BenzCar 类的 属性。
# 这种属性被称为类属性

class BenzCar:
    brand = '奔驰'  # 品牌属性
    country = '德国'  # 产地属性

    # @staticmethod 的修饰， 说明这是该类的一个静态方法
    @staticmethod
    def pressHorn():
        print('嘟嘟~~~~~~')


# 如果我们要得到属性的值可以这样用 类名.属性名 的方式，如下：
print(BenzCar.brand)  # 获取自定义类中的属性

# 要调用执行该类的静态方法，像这样就可以了
BenzCar.pressHorn()  # 调用自定义类中的方法

# 2.类的实例
# 2.1.类和实例的关系
# Python中 类 是 某种对象的类型。
# 而一个个具体的 对象称为 该类型的 实例，比如：
# 数字对象 3 是 int 类型的的实例，具有int类型的特征；
# 字符串对象 ‘abc’ 是 str 类型的实例，具有str类型的特性（比如可以执行str的所有方法，比如 find， split等）；
# 列表对象 [1,2,3] 是 list 类型的的实例，具有list类型的特性（比如可以执行list的所有方法，比如 reverse，append等）；
# 人 就是 一个 类， 而 关羽、张飞 就是 人 这个类的 具体实例。
# 狗 也是 一个 类， 而 你们家的阿黄 和 隔壁家的旺财 就是狗 这个类的 具体实例。

# 而我们自定义的类，也可以产生该类的实例对象。 每个实例对象就是该类的一个实例，具有该类的一切特征。
# 要产生一个类的实例对象，只需要 在类名后面加上括号，就可以了，就会返回一个该类的实例对象。
# 比如：
car1 = BenzCar()
# car1 变量就对应了一个 BenzCar 类型 的实例对象，具有 BenzCar 类的一切属性和方法。
print(car1.brand)
car1.pressHorn()
# 同样，我们也可以用 type 函数查看 car1 这个实例的类型，如下所示:
print(type(car1))

# 2.2.实例属性和实例方法
# 刚才我们定义的类里面的属性都是 类属性 ，里面的方法都是类的 静态方法 。
# 所有BenzCar类的实例对象，其 品牌名 brand ，对应的类属性应该是相同的。
# 就是说下面这样的两个实例
car2 = BenzCar()
car3 = BenzCar()


# car1 和 car2 的 brand属性 都是一样的 值， 都是字符串 ‘奔驰’
# 很好理解，因为品牌这样的属性 对于所有的 奔驰车都是一样的，都是 ‘奔驰’。
# 类属性 是类的共同特征属性。
# 但是有些属性，比如颜色、发动机编号 是每一辆奔驰车 都不同的。
# 所以，在我们定义的 类BenzCar 里面， 颜色、发动机编号 是 不应该 作为类属性的。
# 每个实例独有的属性，称之为 类的实例属性
# 实例属性通常是在类的 初始化方法 __init__ 里面定义的。
# 比如：
class BenzCar1:
    brand = '奔驰'
    country = '德国'

    @staticmethod
    def pressHorn():
        print('嘟嘟~~~~~~')

    # 初始化方法, 注意前后各有两个下划线
    def __init__(self):
        self.color = 'red'  # 颜色
        self.engineSN = '837873398'  # 发动机编号


# 上面的初始化方法 __init__ ，就创建了两个实例属性 color 和 engineSN。

# 为什么 __init__ 方法 叫初始化方法呢？
# 解释器在执行 像下面这样的 实例化类对象 的代码时，
car4 = BenzCar1()


# 首先，解释器会 在内存中 创建一个该类 的 实例对象；
# 然后，解释器会查看这个类是否有 __init__方法，如果有，就会去调用它。
# __init__ 是 创建好实例后 立即就要 执行 的方法，所以称之为初始化方法。

# 通常我们会在__init__方法里面 执行一些初始化的动作，主要就是创建该实例的 实例属性。
# __init__ 方法的第一个参数是 self， 它 是干什么用的呢？
# 刚才说了， 解释器执行实例化代码，会先在内存中创建该类实例对象，然后调用类 的__init__方法。
# 调用 __init__方法时，就将实例对象 传递给 self参数。
# self 参数变量 指向的 就是 实例对象 本身， 所以下面的代码就是创建该实例的属性 color 和 engineSN 了

# self.color  =  'red'         # 颜色
# self.engineSN = '8378738398'  # 发动机编号

# 类的静态方法要在方法定义 上面加上 @staticmethod 的修饰。
# 而 类的 实例方法 不需要任何修饰。
# 通常类的实例方法，都是要 访问类的实例属性的。 包括： 创建、修改、删除 类的实例属性。
# 因为 实例方法 就是要操作 实例独有的属性，否则不操作任何实例属性的话，就应该定义为 类方法。
# 比如 __init__ 初始化方法，就是一个实例方法，它通常要创建一些实例属性。
# 而 pressHorn 方法是类的静态方法， 静态方法是不能访问实例属性的。


# 有时候，实例属性的取值，不是固定写在初始化方法的代码里面。
# 比如这里，每辆车的颜色、发动机号都是不同的，我们应该作为参数传进去。
# 所以修改代码为这样
class BenzCar2:
    brand = '奔驰'
    country = '德国'

    @staticmethod
    def pressHorn():
        print('嘟嘟~~~~~~')

    def __init__(self, color, engineSN):
        self.color = color  # 颜色
        self.engineSN = engineSN  # 发动机编号


# 这样我们在创建实例的时候，就可以根据需要指定不同的实例属性了，比如:
car11 = BenzCar2('白色', '24503425527866')
car22 = BenzCar2('黑色', '34598423586877')
print(car11.color)
print(car22.color)
print(car11.engineSN)
print(car22.engineSN)


# 虽然定义的时候， __init__ 方法 有3个参数 ： self, color, engineSN
# 但是我们这样调用 BenzCar() 实例化的时候， 只需要传入后面两个参数即可。
# 因为 self 参数 需要传入实例对象本身，解释器会自动帮我们传入。
# 其它的 实例方法也是这样， 比如我们定义一个 修改车身颜色的方法 changeColor
class BenzCar3:
    brand = '奔驰'
    country = '德国'

    @staticmethod
    def pressHorn():
        print('嘟嘟~~~~~~')

    def __init__(self, color, engineSN):
        self.color = color  # 颜色
        self.engineSN = engineSN  # 发动机编号

    def changeColor(self, newColor):
        self.color = newColor


car13 = BenzCar3('白色', '24503425527866')
car13.changeColor('黑色')

print(car13.color)


# 调用 changeColor 方法的时候，只需要传入参数 newColor 对应新的颜色即可。
# 不需要我们传入self参数，self 参数是实例对象本身，解释器会自动帮我们传入。
# 注意: 如果你的实例属性名称 和 静态属性 重复了 ，通过类实例访问该属性，访问的是实例属性。通过类名访问该属性，访问的是类属性。
# 比如：
class Car:
    brand = '奔驰'
    name = 'Car'

    def __init__(self):
        # 可以通过实例访问到类属性
        print(self.brand)

        # 定义实例属性和类属性重名
        self.name = 'benz car'


c1 = Car()
print(f'通过实例名访问 name：{c1.name}')
print(f'通过类名访问 name：{Car.name}')


# 一旦创建了 和类属性同名的 实例属性，通过实例访问的就是实例属性了。


# 3.类之间的关系
# 3.1.继承关系
# 真实世界中，类型之间 可能存在 范围 包含关系。
# 比如：人 这个类型 和 亚洲人 这个类型。
# 人 是包括了 亚洲人 的。 如果 某人 是一个 亚洲人，那么它必定是一个 人。
# 这种关系，编程语言中称之为 继承关系。
# 比如上面的例子， 亚洲人 这个类 就 继承 了 人 这个类。
# 通常我们把被继承的类称之为 父类 或者叫 基类
# 把继承类称之为 子类 或者 派生类。
# 同样的，以车为例， 上面我们定义了奔驰车 这个类， 我们还可以定义两个 子类： 奔驰2016 和 奔驰2018 对应两种不同款的奔驰车。
# 如下所示：
class BenzCar4:
    brand = '奔驰'
    country = '德国'

    @staticmethod
    def pressHorn():
        print('嘟嘟~~~~~~')

    def __init__(self, color, engineSN):
        self.color = color  # 颜色
        self.engineSN = engineSN  # 发动机编号

    def changeColor(self, newColor):
        self.color = newColor


class Benz2016(BenzCar4):
    price = 580000
    model = 'Benz2016'


class Benz2018(BenzCar4):
    price = 880000
    model = 'Benz2018'


# 大家可以发现定义子类的时候，必须指定它的父类是什么。
# 指定的方法就是在类名的后面的括号里写上父类的名字。

# 大家注意： 子类会自动拥有父类的一切属性和方法
# 为什么？ 因为一个子类的实例对象 ，必定也是一个父类的实例对象。 当然需要拥有父类的一切属性和方法。
# 就像 一个亚洲人 当然 拥有一个 人 所应该具有的一切特性。
# 比如，执行下面的代码
car14 = Benz2016('red', '234234545622')
car24 = Benz2018('blue', '111135545988')

print(car14.brand)
print(car14.country)
car14.changeColor('black')

print(car24.brand)
print(car24.country)
car24.pressHorn()


# 输出结果如下:
# 奔驰
# 德国
# 奔驰
# 德国
# 嘟嘟~~~~~~

# 一个子类在继承父类的一切特性的基础上，可以有自己的属性和方法。 比如：
class FirstBenz2018(BenzCar):
    price = 880000
    model = 'Benz2018'

    def __init__(self, color, engineSN, weight):
        # 先调用父类的初始化方法
        BenzCar4.__init__(self, color, engineSN)
        self.weight = weight  # 车的重量
        self.oilweight = 0  # 油的重量

    # 加油
    def fillOil(self, oilAdded):
        self.oilweight += oilAdded
        self.weight += oilAdded


# 这里子类 FirstBenz2018 ，新增了两个类属性 价格： price  型号： model，新增了两个实例属性 整车重量：weight  油的重量：oilweight
# 新增了一个实例方法 fillOil ， 对应 加油这个行为。
# 这个行为会导致 实例属性 weight 和 oilweight 变化，所以必须是 实例方法。
# 这样定义好了以后， 就可以创建该类的实例，并访问其新的方法和属性了。
car25 = FirstBenz2018('blue', '111135545988', 1500)
print(car25.oilweight)  # 0
print(car25.weight)  # 1500
car25.fillOil(50)
print(car25.oilweight)  # 50
print(car25.weight)  # 1550


# 3.2.类的组合关系
# 除了上面的继承关系， 类之间还有一种常见的组合关系。
# 所谓组合关系，就是一个类实例的属性里面包含另外一个类实例。
# 比如：
class BenzCar5:
    brand = '奔驰'
    country = '德国'

    def __init__(self, color, engineSN):
        self.color = color  # 颜色
        self.engineSN = engineSN  # 发动机编号


# 这样的定义，类 BenzCar 中 brand 属性就是一个字符串对象 奔驰
# country 属性就是一个字符串对象 德国
# 而该类的实例对象中，就包含了 两个属性 color 和 engineSN， 都是字符串对象
# 我们可以说 该类由 一些字符串对象 组合 而成。
# 甚至还可以包含 我们自己定义的类的实例，比如：
# 轮胎
class Tire:
    def __init__(self, size, createDate):
        self.size = size  # 尺寸
        self.createDate = createDate  # 出厂日期


class BenzCar7:
    brand = '奔驰'
    country = '德国'

    def __init__(self, color, engineSN, tires):
        self.color = color  # 颜色
        self.engineSN = engineSN  # 发动机编号
        self.tires = tires


# 创建4个轮胎实例对象
tires = [Tire(20, '20160808') for i in range(4)]
car1 = BenzCar7('red', '234342342342566', tires)

# 上面的例子里，奔驰汽车对象就 包含 了4个轮胎 Tire 对象。
# 我们可以说奔驰汽车对象是由 4 个轮胎对象 组合 而成，形成了对象的组合关系。
# 对象的 属性 变量 保存了 组合它的那些对象。
# 组合关系，可以通过上层对象的属性一步的访问到内部对象的属性
# 比如，我们可以通过 BenzCar 对象 访问其内部的轮胎对象
print(car1.tires[0].size)

# Python解释器对这个表达式  car.tires[0].size 是从左到右依次执行的，如下所示:
car1.tires  # BenzCar实例的tires属性，得到一个列表，里面是四个 Tire 实例对象
car1.tires[0]  # 得到BenzCar实例的tires属性列表里面的第1个Tire 实例对象
car1.tires[0].size  # 得到BenzCar实例的tires属性列表里面的第1个Tire 实例对象的size属性
